import java.net.*;
import java.io.*;
import javax.swing.*;

/**
 * The Client Class is the Client Side Component of the Server-Client set up for the JadeTalk Chat System.
 * It displays a simple Graphical User Interface that allows the input of messages and displays recieved output.
 *
 * @author BluJaded Team
 * @version (see Sourceforge.net)
 */
public class Client implements Runnable{
    
    // the InputStream of the socket associated to this handler and client pair
    private ObjectInputStream input;
    
    // the OutputStream of the socket associated to this handler and client pair
    private ObjectOutputStream output;
    
    // Message variable to hold incoming messages
    private Message msg;
    
    // holds a reference to one of the user interfaces that implement the ClientUiInterface Interface
    private ClientUiInterface ui;
    
    // the nickname assosiated with this client
    private String nickname;
        
    // a Thread variable to hold the thread of the client that handles the input from the socket
    private Thread uiListener;
    
    //a switch variable used to terminate the thread cleanly
    private boolean run = true;
    
    /**
     * Creates a Client object associated to the Socket provided and establishes input and output streams for that
     * socket.
     * Creates the desired User Interface for this Client
     *
     * @param   title   the title of the Jframe to be displayed in the title bar.
     * @param   socket  the socket assosiated to the server connection.
     * @param   useGui    an boolean to represent the desired User Interface
     * @param   nick    nickname of client
     *
     * @throws java.io.IOException thrown if either of the ObjectInputStream or ObjectOutputStream fail to initialize
     */
    private Client(String title, Socket socket, boolean useGui, String nick) throws IOException
    {
        // saves the provided nickname to the local variable. Shortens it to 10 characters if it is too long.
        if (nick.length() <= 10) {
            nickname = nick;
        } else {
            nickname = nick.substring(0, 10);
        }

        // if the user has specified the CommandLine User Interface
        if (!useGui) {
            // create the CommandLine User Interface for this Client
            ui = new ClientCmdLnUI(this);
        } else {
            // create the Graphical User Interface for this Client
            ui = new ClientGui(title, this);
        }

        // retrieves the OutputStream of the socket
        OutputStream os = socket.getOutputStream();
        // associates the OutputStream with an ObjectOutputStream Object allowing transfer of serialized objects
        output = new ObjectOutputStream(os);
        // flushes the ObjectOutputStream sending the header required by the paired ObjectInputStream of the
        // ClientHandler
        output.flush();
        
        // retrieves the InputStream of the socket
        InputStream is = socket.getInputStream();
        // associates the InputStream with an ObjectInputStream Object allowing transfer of serialized objects
        input = new ObjectInputStream(is);
        
        // creates a new Thread associated with this Client to process input from the ClientHandler
        uiListener = new Thread(this);
        // and then starts that thread.
        uiListener.start();
        
        // creates a new message object
        msg = new Message();
        // sets the nickname variable of the message to the provided nickname parameter
        msg.setNick(nickname);
        // sends the message to the ClientHandler
        outputMsg(msg);

    }

    /**
     * Writes message to ObjectOutputStream of the Socket.
     *
     * @param msg   Message to be written to OutputStream
     */
    public void writeOutput(Object msg)
    {
        try {
            // write the Message directly to the ObjectOutputStream preventing the outputMsg method from overriding the
            // nickname
            output.writeObject(msg);
        }
        catch (IOException ex) {
            //if an error occurs prints the stack trace to assist debugging
            //ex.printStackTrace();
        }
    }

    /**
     * Accessor method for nickname attribute.
     *
     * @return  nickname of client
     */
    public String getNickname()
    {
        return nickname;
    }

    /**
     * Sets nickname to a new nickname.
     *
     * @param nickname  the new nickname
     */
    public void setNickname(String nickname)
    {
        this.nickname = nickname;
    }

    /**
     * Run method required by the implementation of Runnable is executed when a new Thread, once created, is started.
     * Sets up a loop that displays upon reciept, any message it recieves from its ClientHandler until terminated or
     * an exception occurs.
     * If the thread terminates appropriate clean up occurs.
     */
    public void run() {
        try {
            //begins a loop that is only left when an exception occurs or when the run variable is set to false
            while (run) {
                
                try{
                    //reads a serialized Message object from the ObjectInputStream
                    msg = (Message) input.readObject();
                    //sends the Message to the User Interface for display
                    ui.displayMsg(msg);
                }

                catch (ClassNotFoundException e){
                    //if the object read is not a Message Object then do nothing.
                }
            }
        }
        catch (IOException ex) {
            //if an error occurs prints the stack trace to assist debugging
            //ex.printStackTrace ();
        }
        
        //following code runs once when the run method, and therefore the Thread, terminates
        finally {
            //clears the thread variable
            uiListener = null;
            //tells the User interface to disconnect and stop listening for input
            ui.disconnect();
            
            try {
                //closes the output stream
                output.close();
            } 
            catch (IOException ex) {
                //if an error occurs prints the stack trace to assist debugging
                //ex.printStackTrace ();
            }
        }
    }
    
    /**
     * Sends a Message object to the User Interface to be displayed.
     *
     * @param msg the Message object to be sent
     */
    public void outputMsg(Message msg){
        try{
            msg.setNick(nickname);
            output.writeObject(msg);
            output.flush();
        } 
        catch (IOException ex) {
            //if an error occurs prints the stack trace to assist debugging
            //ex.printStackTrace();
            //terminate the thread cleanly by terminating the loop using the switch variable run
            terminateClient();
        }
    }
    
    /**
     * Terminates the Client cleanly by using the switch variable run.
     */
    public void terminateClient()
    {
        run = false;
    }

    /**
     * Accessor for the private field uiListener to determine if the thread is active.
     *
     * @return  Whether thread is still running
     */
    public boolean stillRunning()
    {
        return(uiListener != null);
    }
    
    
    /**
     * main method provides an execution start and allows commandline execution.
     *
     * @param args The standard commandline arguments parameter.
     *             [0] - The expected arguments are a Hostname or IP of a Jadetalk Server
     *             [1] - the port number that the JadeTalk Server is listening on.
     *             [2] - the desired User Interface ( False = CmdLn / True = Gui )
     *             [3] - a nickname for the user to be displayed alongside messages
     *
     * @throws java.io.IOException is thrown if the socket fails to initialize
     * @throws java.net.UnknownHostException is thrown if the hostname or ip given cannot be resolved
     */
    public static void main(String[] args) throws IOException,UnknownHostException {
        
        // if the required number of parameters is not provided
        if (args.length != 4){
            // throw an exception prompting for correct input
            throw new RuntimeException("Please provide the required input. Syntax: <Host> <Port> <GUI true/false> <Nickname>");
        } 
        else{
            try{
                // create a Socket to the Server host on the port provided
                Socket socket = new Socket(args[0], Integer.parseInt(args[1]));
                // create a Client object using the the provided arguments and the socket created
                new Client("*** JADETALK ***   -   Connected to JadeTalk Server at " + args[0] + " on port " +
                        args[1], socket, Boolean.parseBoolean(args[2]), args[3]);
            } 
            catch (ConnectException e){
                // if the Server is not found inform the user
                if (Boolean.valueOf(args[2])){
                    JOptionPane.showMessageDialog(ServerGui.Sframe, "Server not found at: "+ args[0] +":"+args[1],
                            "Server Not Found", JOptionPane.INFORMATION_MESSAGE);
                }
                else{
                    System.out.println("Server not found at this Host:Port");
                }
            }
        }
    }
}
